import type { CSSProperties } from 'vue'
import type { ScrollDirectionType, InstanceState, SizeType, MetadataType } from './types'

import {
  ref,
  shallowRef,
  watchEffect,
  defineComponent,
  nextTick,
  onMounted,
  onActivated,
  onUpdated,
  onBeforeUnmount
} from 'vue'
import clsx from 'clsx'
import memoizeOne from 'memoize-one'
import { DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT, SCROLLING_DEBOUNCE_INTERVAL } from './constants'
import { requestTimeout, cancelTimeout } from './timer'
import { gridProps } from './props'
import { OverlayScrollbars, ScrollbarsHidingPlugin, SizeObserverPlugin } from 'overlayscrollbars'

OverlayScrollbars.plugin([ScrollbarsHidingPlugin, SizeObserverPlugin])

export default defineComponent({
  name: 'VariableSizeGrid',
  props: gridProps,
  emits: ['scroll'],
  setup(props, { emit, slots, expose }) {
    const memoize = memoizeOne(findNearestCell)

    const outerRef = ref<HTMLElement | null>(null)
    const osInstance = shallowRef<OverlayScrollbars | null>(null)

    const isScrolling = ref(false)
    const scrollUpdateRequested = ref(false)

    const scrollLeft = ref(props.initialScrollLeft)
    const scrollTop = ref(props.initialScrollTop)
    const horizontalScrollDirection = ref<ScrollDirectionType>('forward')
    const verticalScrollDirection = ref<ScrollDirectionType>('forward')

    const pingedLeft = ref(false)
    const pingedRight = ref(false)

    let resetIsScrollingTimeoutId: number | null
    let rowStyleCache: Record<string, CSSProperties> = {}
    let cellStyleCache: Record<string, CSSProperties> = {}
    let headerCellStyleCache: Record<string, CSSProperties> = {}
    const instanceState: InstanceState = {
      estimatedColumnWidth: DEFAULT_CELL_WIDTH,
      estimatedRowHeight: DEFAULT_CELL_HEIGHT,
      lastMeasuredColumnIndex: -1,
      lastMeasuredRowIndex: -1,
      columnMetadataMap: {},
      leftColumnMetadataMap: {},
      rightColumnMetadataMap: {},
      rowMetadataMap: {}
    }

    /**
     * 获取单元格的元数据
     * @param type 列或者行
     * @param index 列或者行的索引
     */
    function getCellMetadata(type: 'column' | 'row', index: number) {
      let cellMetadataMap: Record<string, MetadataType>
      let lastMeasuredIndex: number
      let getCellSize: SizeType
      if (type === 'column') {
        cellMetadataMap = instanceState.columnMetadataMap
        lastMeasuredIndex = instanceState.lastMeasuredColumnIndex
        getCellSize = props.columnWidth
      }
      if (type === 'row') {
        cellMetadataMap = instanceState.rowMetadataMap
        lastMeasuredIndex = instanceState.lastMeasuredRowIndex
        getCellSize = props.rowHeight
      }

      if (index > lastMeasuredIndex) {
        let offset = 0
        if (lastMeasuredIndex >= 0) {
          const itemMetadata = cellMetadataMap[lastMeasuredIndex]
          offset = itemMetadata.offset + itemMetadata.size
        }

        for (let i = lastMeasuredIndex + 1; i <= index; i++) {
          const size = getCellSize(i)
          cellMetadataMap[i] = {
            offset,
            size
          }
          offset += size
        }

        if (type === 'column') {
          instanceState.lastMeasuredColumnIndex = index
        }
        if (type === 'row') {
          instanceState.lastMeasuredRowIndex = index
        }
      }
      return cellMetadataMap[index]
    }

    /**
     * 根据索引查找单元格的left偏移量
     * @param index 索引
     * @returns 偏移量
     */
    function getColumnOffset(index: number) {
      return getCellMetadata('column', index).offset
    }

    /**
     * 根据索引查找单元格的宽度
     * @param index 索引
     * @returns 宽度
     */
    function getColumnWidth(index: number) {
      return instanceState.columnMetadataMap[index].size
    }

    /**
     * 获取列的预估总宽度
     * @returns 已经侦测的真实宽度 + 尚未侦测的预估宽度
     */
    function getEstimatedTotalWidth() {
      const { columnCount } = props
      let totalSizeOfMeasuredColumns = 0

      // 边缘检查
      if (instanceState.lastMeasuredColumnIndex >= columnCount) {
        instanceState.lastMeasuredColumnIndex = columnCount - 1
      }

      // 已经侦测的真实宽度
      if (instanceState.lastMeasuredColumnIndex >= 0) {
        const cellMetaData = instanceState.columnMetadataMap[instanceState.lastMeasuredColumnIndex]
        totalSizeOfMeasuredColumns = cellMetaData.offset + cellMetaData.size
      }

      // 尚未侦测的预估宽度
      const numUnmeasuredItems = columnCount - instanceState.lastMeasuredColumnIndex - 1
      const totalSizeOfUnmeasuredItems = numUnmeasuredItems * instanceState.estimatedColumnWidth

      return totalSizeOfMeasuredColumns + totalSizeOfUnmeasuredItems
    }

    /**
     * 根据索引查找单元格的top偏移量
     * @param index 索引
     * @returns 偏移量
     */
    function getRowOffset(index: number) {
      return getCellMetadata('row', index).offset
    }

    /**
     * 根据索引查找单元格的高度
     * @param index 索引
     * @returns 高度
     */
    function getRowHeight(index: number) {
      return instanceState.rowMetadataMap[index].size
    }

    /**
     * 获取行的预估总高度
     * @returns 已经侦测的真实高度 + 尚未侦测的预估高度
     */
    function getEstimatedTotalHeight() {
      const { rowCount } = props
      let totalSizeOfMeasuredRows = 0

      // 边缘检查
      if (instanceState.lastMeasuredRowIndex >= rowCount) {
        instanceState.lastMeasuredRowIndex = rowCount - 1
      }

      // 已经侦测的真实高度
      if (instanceState.lastMeasuredRowIndex >= 0) {
        const itemMetadata = instanceState.rowMetadataMap[instanceState.lastMeasuredRowIndex]
        totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size
      }

      // 尚未侦测的预估高度
      const numUnmeasuredItems = rowCount - instanceState.lastMeasuredRowIndex - 1
      const totalSizeOfUnmeasuredItems = numUnmeasuredItems * instanceState.estimatedRowHeight

      return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems
    }

    /**
     * 列的渲染范围
     * @returns [startIndex - overscanCount, stopIndex + overscanCount, startIndex, stopIndex]
     */
    function getHorizontalRangeToRender() {
      const { columnCount, overscanColumnCount = 1, rowCount, showHeader, enableHorizontal } = props

      if (columnCount === 0 || (!showHeader && rowCount === 0)) {
        return [0, 0, 0, 0]
      }

      // 不启用水平虚拟化，直接渲染所有列
      if (!enableHorizontal) {
        return [0, columnCount - 1, 0, columnCount - 1]
      }

      const startIndex = getColumnStartIndexForOffset(scrollLeft.value)
      const stopIndex = getColumnStopIndexForStartIndex(startIndex, scrollLeft.value)

      // 每个方向至少多渲染一项
      const overscanBackward =
        !isScrolling.value || horizontalScrollDirection.value === 'backward'
          ? Math.max(1, overscanColumnCount)
          : 1
      const overscanForward =
        !isScrolling.value || horizontalScrollDirection.value === 'forward'
          ? Math.max(1, overscanColumnCount)
          : 1

      return [
        Math.max(0, startIndex - overscanBackward),
        Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)),
        startIndex,
        stopIndex
      ]
    }

    /**
     * 渲染列的起始索引
     * @param scrollLeft 滚动距离
     * @returns startIndex
     */
    function getColumnStartIndexForOffset(scrollLeft: number) {
      return memoize('column', scrollLeft)
    }

    /**
     * 渲染列的末尾索引
     * @param startIndex 起始索引
     * @param scrollLeft 滚动距离
     * @returns stopIndex
     */
    function getColumnStopIndexForStartIndex(startIndex: number, scrollLeft: number) {
      const { columnCount, width } = props

      const cellMetadata = getCellMetadata('column', startIndex)
      const maxOffset = scrollLeft + width

      let offset = cellMetadata.offset + cellMetadata.size
      let stopIndex = startIndex

      while (stopIndex < columnCount - 1 && offset < maxOffset) {
        stopIndex++
        offset += getCellMetadata('column', stopIndex).size
      }

      return stopIndex
    }

    /**
     * 行的渲染范围
     * @returns [startIndex - overscanCount, stopIndex + overscanCount, startIndex, stopIndex]
     */
    function getVerticalRangeToRender() {
      const { columnCount, overscanRowCount = 1, rowCount, showHeader, enableVertical } = props

      if (columnCount === 0 || (!showHeader && rowCount === 0)) {
        return [0, 0, 0, 0]
      }

      // 不启用垂直虚拟化，直接渲染所有行
      if (!enableVertical) {
        return [0, rowCount - 1, 0, rowCount - 1]
      }

      const startIndex = getRowStartIndexForOffset(scrollTop.value)
      const stopIndex = getRowStopIndexForStartIndex(startIndex, scrollTop.value)

      // 每个方向至少多渲染一项
      const overscanBackward =
        !isScrolling.value || verticalScrollDirection.value === 'backward'
          ? Math.max(1, overscanRowCount)
          : 1
      const overscanForward =
        !isScrolling.value || verticalScrollDirection.value === 'forward'
          ? Math.max(1, overscanRowCount)
          : 1

      return [
        Math.max(0, startIndex - overscanBackward),
        Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)),
        startIndex,
        stopIndex
      ]
    }

    /**
     * 渲染行的起始索引
     * @param scrollTop 滚动距离
     * @returns startIndex
     */
    function getRowStartIndexForOffset(scrollTop: number) {
      return memoize('row', scrollTop)
    }

    /**
     * 渲染行的末尾索引
     * @param startIndex 起始索引
     * @param scrollTop 滚动距离
     * @returns stopIndex
     */
    function getRowStopIndexForStartIndex(startIndex: number, scrollTop: number) {
      const { rowCount, height } = props

      const itemMetadata = getCellMetadata('row', startIndex)
      const maxOffset = scrollTop + height

      let offset = itemMetadata.offset + itemMetadata.size
      let stopIndex = startIndex

      while (stopIndex < rowCount - 1 && offset < maxOffset) {
        stopIndex++
        offset += getCellMetadata('row', stopIndex).size
      }

      return stopIndex
    }

    /**
     * 查找最近的索引值
     * @param type 行或列
     * @param offset 滚动距离
     * @returns 索引值
     */
    function findNearestCell(type: 'column' | 'row', offset: number) {
      let cellMetadataMap: Record<string, MetadataType>
      let lastMeasuredIndex: number
      if (type === 'column') {
        cellMetadataMap = instanceState.columnMetadataMap
        lastMeasuredIndex = instanceState.lastMeasuredColumnIndex
      }
      if (type === 'row') {
        cellMetadataMap = instanceState.rowMetadataMap
        lastMeasuredIndex = instanceState.lastMeasuredRowIndex
      }

      const lastMeasuredCellOffset =
        lastMeasuredIndex > 0 ? cellMetadataMap[lastMeasuredIndex].offset : 0

      if (lastMeasuredCellOffset >= offset) {
        // 如果已经计算的距离包含了这个范围，使用二分法查找
        return findNearestCellBinarySearch(type, lastMeasuredIndex, 0, offset)
      } else {
        // 否则使用指数法查找
        return findNearestCellExponentialSearch(type, Math.max(0, lastMeasuredIndex), offset)
      }
    }

    // 二分法
    function findNearestCellBinarySearch(
      type: 'column' | 'row',
      high: number,
      low: number,
      offset: number
    ) {
      while (low <= high) {
        const middle = low + Math.floor((high - low) / 2)
        const currentOffset = getCellMetadata(type, middle).offset

        if (currentOffset === offset) {
          return middle
        } else if (currentOffset < offset) {
          low = middle + 1
        } else if (currentOffset > offset) {
          high = middle - 1
        }
      }

      if (low > 0) {
        return low - 1
      } else {
        return 0
      }
    }

    // 指数法
    function findNearestCellExponentialSearch(
      type: 'column' | 'row',
      index: number,
      offset: number
    ) {
      const itemCount = type === 'column' ? props.columnCount : props.rowCount
      let interval = 1

      while (index < itemCount && getCellMetadata(type, index).offset < offset) {
        index += interval
        interval *= 2
      }

      // 二分法查找
      return findNearestCellBinarySearch(
        type,
        Math.min(index, itemCount - 1),
        Math.floor(index / 2),
        offset
      )
    }

    function scrollUpdatedHandler(instance: OverlayScrollbars) {
      instance.elements().scrollbarVertical.scrollbar.style.top = props.headerHeight + 'px'
    }

    function scrollHandler(event: Event) {
      const {
        clientHeight,
        clientWidth,
        scrollLeft: left,
        scrollTop: top,
        scrollHeight,
        scrollWidth
      } = event.target as HTMLElement

      if (scrollLeft.value !== left || scrollTop.value !== top) {
        const calculatedScrollLeft = Math.max(0, Math.min(left, scrollWidth - clientWidth))
        const calculatedScrollTop = Math.max(0, Math.min(top, scrollHeight - clientHeight))

        isScrolling.value = true

        horizontalScrollDirection.value = scrollLeft.value < left ? 'forward' : 'backward'
        verticalScrollDirection.value = scrollTop.value < top ? 'forward' : 'backward'
        scrollLeft.value = calculatedScrollLeft
        scrollTop.value = calculatedScrollTop

        // 处理固定列
        setPing(scrollWidth)
        scrollUpdateRequested.value = false
        nextTick(() => {
          resetIsScrollingDebounced()
        })
        emit('scroll', left, top)
      }
    }

    /**
     * expose function
     * @param left scrollLeft
     * @param top scrollTop
     */
    function scrollTo(left = 0, top = 0) {
      scrollUpdateRequested.value = scrollLeft.value !== left || scrollTop.value !== top
      if (scrollUpdateRequested.value) {
        if (scrollLeft.value !== left) {
          horizontalScrollDirection.value = scrollLeft.value < left ? 'forward' : 'backward'
          scrollLeft.value = left
        }
        if (scrollTop.value !== top) {
          verticalScrollDirection.value = scrollTop.value < top ? 'forward' : 'backward'
          scrollTop.value = top
        }
        if (osInstance.value) {
          const { viewport } = osInstance.value.elements()
          viewport.scroll(left, top)
        }
      }
      nextTick(() => {
        resetIsScrollingDebounced()
      })
    }

    function resetIsScrollingDebounced() {
      if (resetIsScrollingTimeoutId !== null) {
        cancelTimeout(resetIsScrollingTimeoutId)
      }
      resetIsScrollingTimeoutId = requestTimeout(resetIsScrolling, SCROLLING_DEBOUNCE_INTERVAL)
    }

    // 重置isScrolling
    function resetIsScrolling() {
      resetIsScrollingTimeoutId = null
      isScrolling.value = false
      // 清除样式缓存
      resetStyleCache()
    }

    /**
     * 获取row的样式
     * 此过程为懒加载
     * *
     * *
     * ToDo: header row可能需要特殊处理
     *       多级header的处理
     */
    function getRowStyle(rowIndex: number) {
      const key = 'ROW_' + rowIndex
      let style: CSSProperties
      if (rowStyleCache[key]) {
        style = rowStyleCache[key]
      } else {
        style = {
          position: 'absolute',
          transform: `translateY(${getRowOffset(rowIndex)}px)`,
          height: getRowHeight(rowIndex) + 'px'
        }
        rowStyleCache[key] = style
      }
      return style
    }

    function getHeaderRowStyle() {
      const style: CSSProperties = {
        position: 'absolute',
        transform: 'translateY(0)',
        height: props.headerHeight + 'px'
      }
      return style
    }

    /**
     * 获取cell样式
     * 此过程为懒加载
     * @param rowIndex 行索引
     * @param columnIndex 列索引
     * @returns cell style
     */
    function getCellStyle(rowIndex: number, columnIndex: number, isHeader = false) {
      const { cellKey, headerCellKey, columnCount, leftColumnCount, rightColumnCount } = props

      const key = isHeader
        ? headerCellKey({ rowIndex, columnIndex })
        : cellKey({ rowIndex, columnIndex })
      const cache = isHeader ? headerCellStyleCache : cellStyleCache

      let style: CSSProperties = {}
      if (cache[key]) {
        style = cache[key]
      } else {
        style = {
          position: 'absolute',
          left: getColumnOffset(columnIndex) + 'px',
          width: getColumnWidth(columnIndex) + 'px'
        }
        if (leftColumnCount > 0 || rightColumnCount > 0) {
          if (columnIndex < leftColumnCount) {
            style.left = instanceState.leftColumnMetadataMap[columnIndex].offset + 'px'
          } else if (columnIndex >= columnCount - rightColumnCount) {
            style.left = instanceState.rightColumnMetadataMap[columnIndex].offset + 'px'
          } else {
            if (leftColumnCount > 0) {
              const metadata = instanceState.leftColumnMetadataMap[leftColumnCount - 1]
              style.left = getColumnOffset(columnIndex) - metadata.offset - metadata.size + 'px'
            }
          }
        }
        cache[key] = style
      }
      return style
    }

    /**
     * 重置样式缓存
     */
    function resetStyleCache() {
      cellStyleCache = {}
      headerCellStyleCache = {}
      rowStyleCache = {}
    }

    /**
     * 初始化固定列的元数据
     */
    function initStickyColumnMetadata() {
      const { columnCount, leftColumnCount, rightColumnCount, columnWidth } = props
      if (leftColumnCount > 0) {
        let offset = 0
        for (let i = 0; i < leftColumnCount; i++) {
          const size = columnWidth(i)
          instanceState.leftColumnMetadataMap[i] = {
            offset,
            size
          }
          offset += size
        }
      }
      if (rightColumnCount > 0) {
        let offset = 0
        for (let i = columnCount - rightColumnCount; i < columnCount; i++) {
          const size = columnWidth(i)
          instanceState.rightColumnMetadataMap[i] = {
            offset,
            size
          }
          offset += size
        }
      }
    }

    /**
     * 设置class, 样式优化
     * is-ping-left
     * is-ping-right
     * @param scrollWidth 容器的scrollWidth
     */
    function setPing(scrollWidth: number) {
      pingedLeft.value = props.leftColumnCount > 0 && scrollLeft.value > 0
      pingedRight.value =
        props.rightColumnCount > 0 && Math.floor(scrollWidth - scrollLeft.value) > props.width
    }

    /**
     * 重置已经侦测过列或行
     * *
     * 某些情况会导致已经缓存样式或元信息过期
     * *
     * 例如：
     * 1.列设置 列的顺序发生变化
     * 2.伸缩列，某一列的宽度发生变化
     */
    function resetMeasuredIndicesAfterChange(type: 'column' | 'row', index = 0) {
      if (type === 'column') {
        instanceState.lastMeasuredColumnIndex = Math.min(
          instanceState.lastMeasuredColumnIndex,
          index - 1
        )
      }
      if (type === 'row') {
        instanceState.lastMeasuredRowIndex = Math.min(instanceState.lastMeasuredRowIndex, index - 1)
      }
      resetStyleCache()
    }

    function initializeOS() {
      osInstance.value = OverlayScrollbars(
        outerRef.value as HTMLElement,
        {
          scrollbars: {
            autoHide: 'leave',
            autoHideDelay: 300
          }
        },
        {
          scroll: (_instance: OverlayScrollbars, event: Event) => scrollHandler(event),
          updated: (instance: OverlayScrollbars) => scrollUpdatedHandler(instance)
        }
      )
    }

    function restoreOS() {
      if (osInstance.value) {
        const { viewport } = osInstance.value.elements()
        if (viewport.offsetHeight > 0) {
          viewport.scrollLeft = scrollLeft.value
          viewport.scrollTop = scrollTop.value
          setPing(viewport.scrollWidth)
        } else {
          // 等待下一帧再尝试
          requestAnimationFrame(restoreOS)
        }
      }
    }

    watchEffect(() => {
      const { width, height } = props
      if (osInstance.value) {
        const { viewport } = osInstance.value.elements()
        viewport.style.width = width + 'px'
        viewport.style.height = height + 'px'
      }
    })

    onMounted(() => {
      const { initialScrollLeft, initialScrollTop, estimatedColumnWidth, estimatedRowHeight } =
        props

      if (estimatedColumnWidth) {
        instanceState.estimatedColumnWidth = estimatedColumnWidth
      }
      if (estimatedRowHeight) {
        instanceState.estimatedRowHeight = estimatedRowHeight
      }

      nextTick(() => {
        initializeOS()
        if (osInstance.value) {
          const { viewport } = osInstance.value.elements()
          if (typeof initialScrollLeft === 'number') {
            viewport.scrollLeft = initialScrollLeft
          }
          if (typeof initialScrollTop === 'number') {
            viewport.scrollTop = initialScrollTop
          }
          setPing(viewport.scrollWidth)
        }
      })
    })

    onUpdated(() => {
      if (osInstance.value && scrollUpdateRequested.value) {
        const { viewport } = osInstance.value.elements()
        viewport.scrollLeft = Math.max(0, scrollLeft.value)
        viewport.scrollTop = Math.max(0, scrollTop.value)
      }
    })

    onBeforeUnmount(() => {
      if (resetIsScrollingTimeoutId !== null) {
        cancelTimeout(resetIsScrollingTimeoutId)
      }
    })

    onActivated(() => {
      restoreOS()
    })

    expose({ scrollTo, resetMeasuredIndicesAfterChange })

    return () => {
      const {
        width,
        height,
        headerHeight,
        rowCount,
        columnCount,
        leftColumnCount,
        rightColumnCount,
        cellKey,
        headerCellKey,
        headerCellRenderer,
        cellRenderer,
        rowRenderer
      } = props

      // class前缀
      const prefixCls = 'virtualized-grid'

      // 首先处理固定列的元数据
      initStickyColumnMetadata()
      // 获取列的渲染范围
      let [columnStartIndex, columnStopIndex] = getHorizontalRangeToRender()
      columnStartIndex += leftColumnCount
      columnStopIndex -= rightColumnCount
      const [rowStartIndex, rowStopIndex] = getVerticalRangeToRender()

      const centerItems: JSX.Element[] = []
      const centerHeaderItems: JSX.Element[] = []
      const leftItems: JSX.Element[] = []
      const leftHeaderItems: JSX.Element[] = []
      const rightItems: JSX.Element[] = []
      const rightHeaderItems: JSX.Element[] = []

      // header 内容处理
      if (leftColumnCount > 0) {
        const headerCells: JSX.Element[] = []
        for (let columnIndex = 0; columnIndex < leftColumnCount; columnIndex++) {
          const key = headerCellKey({ rowIndex: 0, columnIndex })
          const style = getCellStyle(0, columnIndex, true)
          const headerCell = headerCellRenderer({
            rowIndex: 0,
            columnIndex,
            style,
            key
          })
          if (headerCell) {
            headerCells.push(headerCell)
          }
        }
        leftHeaderItems.push(
          rowRenderer({
            rowIndex: 0,
            cells: headerCells,
            key: `HEADER_ROW_L_0`,
            style: getHeaderRowStyle(),
            isHeader: true
          })
        )
      }
      if (columnCount - leftColumnCount - rightColumnCount > 0) {
        const headerCells: JSX.Element[] = []
        for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
          const key = headerCellKey({ rowIndex: 0, columnIndex })
          const style = getCellStyle(0, columnIndex, true)
          const headerCell = headerCellRenderer({
            rowIndex: 0,
            columnIndex,
            style,
            key
          })
          if (headerCell) {
            headerCells.push(headerCell)
          }
        }
        centerHeaderItems.push(
          rowRenderer({
            rowIndex: 0,
            cells: headerCells,
            key: `HEADER_ROW_C_0`,
            style: getHeaderRowStyle(),
            isHeader: true
          })
        )
      }
      if (rightColumnCount > 0) {
        const headerCells: JSX.Element[] = []
        for (
          let columnIndex = columnCount - rightColumnCount;
          columnIndex < columnCount;
          columnIndex++
        ) {
          const key = headerCellKey({ rowIndex: 0, columnIndex })
          const style = getCellStyle(0, columnIndex, true)
          const headerCell = headerCellRenderer({
            rowIndex: 0,
            columnIndex,
            style,
            key
          })
          if (headerCell) {
            headerCells.push(headerCell)
          }
        }
        rightHeaderItems.push(
          rowRenderer({
            rowIndex: 0,
            cells: headerCells,
            key: `HEADER_ROW_R_0`,
            style: getHeaderRowStyle(),
            isHeader: true
          })
        )
      }
      // body 内容处理
      if (columnCount > 0 && rowCount > 0) {
        for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
          if (leftColumnCount > 0) {
            const cells: JSX.Element[] = []
            for (let columnIndex = 0; columnIndex < leftColumnCount; columnIndex++) {
              const key = cellKey({ rowIndex, columnIndex })
              const style = getCellStyle(rowIndex, columnIndex)
              const cell = cellRenderer({
                rowIndex,
                columnIndex,
                style,
                key
              })
              if (cell) {
                cells.push(cell)
              }
            }
            leftItems.push(
              rowRenderer({
                rowIndex,
                cells,
                key: `ROW_L_${rowIndex}`,
                style: getRowStyle(rowIndex)
              })
            )
          }
          if (columnCount - leftColumnCount - rightColumnCount > 0) {
            const cells: JSX.Element[] = []
            for (
              let columnIndex = columnStartIndex;
              columnIndex <= columnStopIndex;
              columnIndex++
            ) {
              const key = cellKey({ rowIndex, columnIndex })
              const style = getCellStyle(rowIndex, columnIndex)
              const cell = cellRenderer({
                rowIndex,
                columnIndex,
                style,
                key
              })
              if (cell) {
                cells.push(cell)
              }
            }
            centerItems.push(
              rowRenderer({
                rowIndex,
                cells,
                key: `ROW_C_${rowIndex}`,
                style: getRowStyle(rowIndex)
              })
            )
          }
          if (rightColumnCount > 0) {
            const cells: JSX.Element[] = []
            for (
              let columnIndex = columnCount - rightColumnCount;
              columnIndex < columnCount;
              columnIndex++
            ) {
              const key = cellKey({ rowIndex, columnIndex })
              const style = getCellStyle(rowIndex, columnIndex)
              const cell = cellRenderer({
                rowIndex,
                columnIndex,
                style,
                key
              })
              if (cell) {
                cells.push(cell)
              }
            }
            rightItems.push(
              rowRenderer({
                rowIndex,
                cells,
                key: `ROW_R_${rowIndex}`,
                style: getRowStyle(rowIndex)
              })
            )
          }
        }
      }

      /**
       * 这个数值可能是不准确的，因为他的结果来源于
       * 已经加载的item的宽度 + 未加载的单元格的宽度 x 估计值
       * *
       * 当所所有的单元格加载完成时，他的结果将不再包含估计值
       * 此时结果是准确的
       * *
       * 我们会在滚动加载的过程中不断修正此结果
       */
      const estimatedTotalHeight = getEstimatedTotalHeight()
      const estimatedTotalWidth = getEstimatedTotalWidth()

      const outerClass = clsx(prefixCls, {
        'has-fix-left': leftColumnCount > 0,
        'has-fix-right': rightColumnCount > 0,
        'is-ping-left': pingedLeft.value,
        'is-ping-right': pingedRight.value
      })

      // 外层容器尺寸
      const outerStyle: CSSProperties = {
        width: width + 'px',
        height: height + 'px'
      }
      // 空容器宽度
      const emptyStyle: CSSProperties = {
        width: width + 'px'
      }
      /**
       * 内容容器高度
       * *
       * include
       * fixed left
       * center
       * fixed right
       */
      const innerStyle = {
        height: estimatedTotalHeight + headerHeight + 'px'
      }
      // fixed容器宽度计算
      let leftWidth = 0
      let rightWidth = 0
      if (leftColumnCount > 0) {
        leftWidth =
          instanceState.leftColumnMetadataMap[leftColumnCount - 1].offset +
          instanceState.leftColumnMetadataMap[leftColumnCount - 1].size
      }
      if (rightColumnCount > 0) {
        rightWidth =
          instanceState.rightColumnMetadataMap[columnCount - 1].offset +
          instanceState.rightColumnMetadataMap[columnCount - 1].size
      }
      // fixed left容器尺寸 body & header
      const leftStyle: CSSProperties = {
        width: leftWidth + 'px',
        height: estimatedTotalHeight + 'px'
      }
      const leftHeaderStyle: CSSProperties = {
        width: leftWidth + 'px',
        height: headerHeight + 'px'
      }
      // center容器尺寸 body & header
      const centerStyle: CSSProperties = {
        width: estimatedTotalWidth - leftWidth - rightWidth + 'px',
        height: estimatedTotalHeight + 'px'
      }
      const centerHeaderStyle: CSSProperties = {
        width: estimatedTotalWidth - leftWidth - rightWidth + 'px',
        height: headerHeight + 'px'
      }
      // fixed right容器尺寸 body & header
      const rightStyle: CSSProperties = {
        width: rightWidth + 'px',
        height: estimatedTotalHeight + 'px'
      }
      const rightHeaderStyle: CSSProperties = {
        width: rightWidth + 'px',
        height: headerHeight + 'px'
      }

      const items: JSX.Element[] = []
      if (leftColumnCount > 0) {
        items.push(
          <div class={`${prefixCls}-left`} style={innerStyle}>
            <div class={`${prefixCls}-header`} style={leftHeaderStyle}>
              {leftHeaderItems}
            </div>
            {rowCount > 0 && (
              <div class={`${prefixCls}-body`} style={leftStyle}>
                {leftItems}
              </div>
            )}
          </div>
        )
      }
      if (columnCount - leftColumnCount - rightColumnCount > 0) {
        items.push(
          <div class={`${prefixCls}-center`} style={innerStyle}>
            <div class={`${prefixCls}-header`} style={centerHeaderStyle}>
              {centerHeaderItems}
            </div>
            {rowCount > 0 ? (
              <div class={`${prefixCls}-body`} style={centerStyle}>
                {centerItems}
              </div>
            ) : (
              <div class={`${prefixCls}-empty`}>
                <div class={`${prefixCls}-empty-inner`} style={emptyStyle}>
                  {slots.empty && slots.empty()}
                </div>
              </div>
            )}
          </div>
        )
      }
      if (rightColumnCount > 0) {
        items.push(
          <div class={`${prefixCls}-right`} style={innerStyle}>
            <div class={`${prefixCls}-header`} style={rightHeaderStyle}>
              {rightHeaderItems}
            </div>
            {rowCount > 0 && (
              <div class={`${prefixCls}-body`} style={rightStyle}>
                {rightItems}
              </div>
            )}
          </div>
        )
      }

      return (
        <div ref={outerRef} class={outerClass} style={outerStyle}>
          <div class="virtualized-grid-inner" style={{ width: estimatedTotalWidth + 'px' }}>
            {items}
          </div>
        </div>
      )
    }
  }
})
